﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Runtime.Serialization;
using System.Web;
using Portal.Data.Helper;
using Portal.Core;
using Portal.Core.Binding.FormBuilder;
using Portal.Core.Helper;
using System.Linq;
using Portal.Core.Utils;
using Portal.Data.Security;

namespace Portal.Data.Entity
{
    [Table(Name = "dbo.Book"), Serializable, DataContractAttribute]
    public class Book : BaseObject
    {
        #region Ctor
        public Book()
        {
            CreatedDate =
            ModifiedDate = DateTime.Now;
            Status = true;
        }
        #endregion

        #region Properties
        [SkipBinding]
        [Column(IsPrimaryKey = true, IsDbGenerated = true)]
        public int ID { get; set; }

        [Column]
        public string FullName { get; set; }
        [Column]
        public string SafeName { get; set; }

        [Column]
        public string SortName { get; set; }

        [Column]
        public string OtherName { get; set; }

        [Column]
        public string OtherSortName { get; set; }

        [Column]
        public string Description { get; set; }

        [Column, Builder(DataType = DataType.Image), Image(SavePath = "Files/Book/")]
        public string Image { get; set; }

        [Column]
        public int AuthorId { get; set; }

        [Column]
        public int Visited { get; set; }

        [Column]
        public bool Status { get; set; }

        [Column]
        public string MetaTitle { get; set; }

        [Column]
        public string MetaDesc { get; set; }

        [Column]
        public string MetaKeyword { get; set; }

        [Column, SkipBinding]
        public DateTime CreatedDate { get; set; }

        [Column, SkipBinding]
        public DateTime ModifiedDate { get; set; }

        [Column]
        public string Source { get; set; }

        [Column]
        public bool IsHot { get; set; }

        [Column]
        public bool Finish { get; set; }

        [Column]
        public string Types { get; set; }
        [Column]
        public string HistoryTypeIds { get; set; }
        [Column]
        public string CrawlAuthor { get; set; }
        [Column]
        public string CrawlUrl { get; set; }
        [Column]
        public string CrawlName { get; set; }
        [Column]
        public string CrawlHost { get; set; }

        [Column]
        public int RateTotal { get; set; }
        [Column]
        public double RateAverage { get; set; }

        [Column]
        public int LastChapId { get; set; }
        [Column]
        public int TotalChap { get; set; }
        [Column]
        public bool IsUnique { get; set; }
        #endregion

        #region extension props
        [SkipBinding]
        public string DisplayTitle
        {
            get
            {
                return HttpUtility.HtmlDecode(FullName);
            }
        }
        [SkipBinding]
        public string TotalVisit
        {
            get
            {
                return Process.GetTotalDisplay(Visited);
            }
        }
        [SkipBinding]
        public Content LastChap
        {
            get
            {
                return Content.FindByPrimaryKey(LastChapId);
            }
        }
        [SkipBinding]
        public string LastChapUrl
        {
            get
            {
                return LastChap != null ? LastChap.URL : "#";
            }
        }
        [SkipBinding]
        public int LastChapter
        {
            get
            {
                return LastChap != null ? LastChap.Chapter : 0;
            }
        }
        [SkipBinding]
        public string LastChapTitle
        {
            get
            {
                return LastChap != null ? LastChap.Title : "";
            }
        }
        [SkipBinding]
        public string LastChapIsNew
        {
            get
            {
                if (LastChap != null)
                {
                    if (DateTime.Now.Date == LastChap.CreatedDate.Date)
                        return "<div class='new'></div>";
                }
                return "";
            }
        }
        [SkipBinding]
        public DateTime LastChapDate
        {
            get
            {
                return LastChap != null ? LastChap.CreatedDate : default(DateTime);
            }
        }
        /// <summary>
        /// Get url like: Cat/Title/ID.extension
        /// </summary>
        [SkipBinding]
        public string Url
        {
            get
            {
                return string.Format(Const.UrlBookFormat, "/", SafeName, ID, CoreContext.UrlExtension);
            }
        }
        [SkipBinding]
        public string FinishDisplay
        {
            get
            {
                return Finish ? "Đủ bộ" : "Còn tiếp";
            }
        }
        [SkipBinding]
        public string CssFull
        {
            get { return Finish ? "fullbook" : ""; }
        }

        public string DescriptionSort()
        {
            return Process.ConvertToText(Description, 300);
        }
        public string SeoDescription()
        {
            return !string.IsNullOrEmpty(MetaDesc) ? MetaDesc : DescriptionSort();
        }
        public string SeoKeywords()
        {
            return !string.IsNullOrEmpty(MetaKeyword) ? MetaKeyword : Common.GetString("Default", "META_Keywords");
        }

        public string HistoryTypes()
        {
            var arrIds = (HistoryTypeIds ?? "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x));
            var results = HistoryType.ListAll().Where(x => arrIds.Contains(x.ID)).ToList();
            var s = results.Aggregate("", (current, ht) => current + string.Format("<a href='{0}' class='booktype'>{1}</a>, ", ht.Url, ht.FullName));
            return string.IsNullOrEmpty(s) ? "" : s.Remove(s.Length - 2);
        }
        [SkipBinding]
        public string QuickInfo
        {
            get
            {
                var info = String.Format("<div class='infobottom'><div class='col1'>Lượt xem:<br/><label>{0}</label></div>", TotalVisit);
                info += String.Format("<div class='col2'>Trạng thái:<br/><label>{0}</label></div>", FinishDisplay);
                info += String.Format("<div class='col3'>Số chương:<br/><label>{0}</label></div>", TotalChap);
                info += String.Format("<div class='col4'>Cập nhật:<br/><label>{0}</label></div>", Common.FriendlyDateDisplay(ModifiedDate));
                info += "</div>";
                return DescriptionSort().Replace("\"", "'") + info;
            }
        }
        #endregion

        #region DAL
        private const string SORT_FORMAT = " ORDER BY {0}.FullName";

        public static Book FindByPrimaryKey(object objectid)
        {
            return FindByPrimaryKey<Book>(objectid);
        }

        public static IList FindByAuthor(int authorId, int max, int ignoreBookId = 0)
        {
            if (authorId == 0) return new ArrayList();

            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "FindByAuthor", authorId, max, ignoreBookId };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (IList)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var sBookIds = context.BookByAuthors.Where(x => x.AuthorId == authorId).Select(x => x.BookIds).FirstOrDefault();
                if (sBookIds != null)
                {
                    var bIds = ToIntArray(sBookIds);
                    var results = context.Books.Where(x => bIds.Contains(x.ID) && x.ID != ignoreBookId && x.Status).OrderBy(x => x.FullName).Take(max).ToList();
                    CacheHelper.Instance.Set(parameter, results);
                    return results;
                }
            }
            return new ArrayList();
        }
        public static List<Book> PagingByAuthor(int authorId, int pageindex)
        {
            if (authorId <= 0) return new List<Book>();

            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "PagingByAuthor", authorId, pageindex };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Book>)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var sBookIds = context.BookByAuthors.Where(x => x.AuthorId == authorId).Select(x => x.BookIds).FirstOrDefault();
                if (sBookIds != null)
                {
                    var bIds = ToIntArray(sBookIds);
                    var results = context.Books.Where(x => bIds.Contains(x.ID) && x.Status).OrderBy(x => x.FullName).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                    CacheHelper.Instance.Set(parameter, results);
                    return results;
                }
            }
            return new List<Book>();
        }
        public static List<Book> PagingByType(int typeId, int pageindex)
        {
            if (typeId <= 0) return new List<Book>();

            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "PagingByType", typeId, pageindex };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Book>)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var sBookIds = context.BookTypes.Where(x => x.TypeId == typeId).Select(x => x.BookIds).FirstOrDefault();
                if (sBookIds != null)
                {
                    var bIds = ToIntArray(sBookIds).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault);
                    var results = context.Books.Where(x => bIds.Contains(x.ID) && x.Status).ToList();
                    //var results = context.Books.Where(x => bIds.Contains(x.ID) && x.Status).OrderBy(x => x.FullName).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                    CacheHelper.Instance.Set(parameter, results);
                    return results;
                }
            }
            return new List<Book>();
        }
        public static List<Book> PagingFrontEnd(int pageindex)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "PagingFrontEnd", pageindex };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Book>)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => x.Status).OrderBy(x => x.FullName).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }

        /// <summary>
        /// Search in SortName + OtherSortName
        /// </summary>
        /// <param name="keyword"></param>
        /// <param name="pageindex"></param>
        /// <returns></returns>
        public static List<Book> QuickSearch(string keyword, int pageindex)
        {
            var sortName = GetSortName(keyword);
            using (var context = new CmsDataContext())
            {
                return (IsAdminPage())
                    ? context.Books.Where(x => x.SortName.Contains(sortName) || x.OtherSortName.Contains(sortName)).OrderByDescending(x => x.Visited).Skip(pageindex * PageSizeDefault).Take(PageSizeDefault).ToList()
                              : context.Books.Where(x => (x.SortName.Contains(sortName) || x.OtherSortName.Contains(sortName)) && x.Status).OrderByDescending(x => x.Visited).Skip(pageindex * PageSizeDefault).Take(PageSizeDefault).ToList();
            }
        }

        /// <summary>
        /// Using for backend only
        /// </summary>
        /// <param name="objSearch"></param>
        /// <param name="pagesize"></param>
        /// <param name="pageindex"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static List<Book> Search(BookSearchObject objSearch, int pagesize, int pageindex, out int count)
        {
            string sqlSelect = "select c.* from Book c";
            string sqlWhere = " where c.ID>0 ";

            //by key
            if (!string.IsNullOrEmpty(objSearch.Key))
            {
                var sortName = GetSortName(objSearch.Key);
                sqlWhere += " and (";
                sqlWhere += string.Format(" c.SortName like N'%{0}%'", sortName);
                sqlWhere += string.Format(" OR c.OtherSortName like N'%{0}%'", sortName);
                sqlWhere += ")";
            }
            //by the loai ID
            if (objSearch.BookTypeId > 0)
            {
                var bookIds = BookType.GetBookIds(objSearch.BookTypeId);
                if (bookIds.Length == 0)
                {
                    count = 0;
                    return new List<Book>();
                }
                sqlWhere += " and c.Id in (select BookIds from BookType where TypeId=" + string.Join(",", bookIds) + ")";
            }
            //crawl host
            if (!string.IsNullOrEmpty(objSearch.CrawlHost))
            {
                var crh = objSearch.CrawlHost.ToLower();
                sqlWhere += (crh == "truyengi")
                                    ? " and (c.CrawlHost is null or c.CrawlHost='truyengi')"
                                    : " and c.CrawlHost='" + crh + "'";
            }
            //status
            if (objSearch.Status >= 0)
            {
                sqlWhere += " and c.Status=" + objSearch.Status;
            }
            //finish status
            if (objSearch.FindFinish >= 0)
            {
                sqlWhere += " and c.Finish=" + objSearch.FindFinish;
            }
            //finish status
            if (objSearch.FindHot >= 0)
            {
                sqlWhere += " and c.IsHot=" + objSearch.FindHot;
            }
            //finish status
            if (objSearch.FindUniquee >= 0)
            {
                sqlWhere += " and c.IsUnique=" + objSearch.FindUniquee;
            }
            //by author
            if (objSearch.Author >= 0)
            {
                sqlWhere += " and c.AuthorId=" + objSearch.Author;
            }

            //order by clause
            var orderbyClause = " ORDER BY c." + objSearch.OrderByFieldName + " " + objSearch.OrderByAct;

            int firstResult = pagesize * pageindex;
            var list = LINQHelper.SlicedFindAll<Book>(sqlSelect + sqlWhere, orderbyClause, firstResult, pagesize, out count);
            return list;
        }

        public static IList FindByIds(string ids, string ignoreIds = null)
        {

            if (!string.IsNullOrEmpty(ids)) return new ArrayList();
            var arrIds = ToIntArray(ids);
            var arrIgnore = ToIntArray(ignoreIds);

            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "FindByIds", ids, ignoreIds };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (IList)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => arrIds.Contains(x.ID) && !arrIgnore.Contains(x.ID) && x.Status).OrderBy(x => x.FullName).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }

        public static List<Book> FindHots(int pageindex, bool renewCaching = false)
        {
            var parameter = new object[] { typeof(Book).Name, "FindHots", pageindex };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                {
                    return (List<Book>)obj;
                }
            }
            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => x.IsHot && x.Status).OrderByDescending(x => x.Visited).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }

        public static List<Book> FindByView(int pageindex)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "FindByView", pageindex };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
            {
                return (List<Book>)obj;
            }
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => x.Status).OrderByDescending(x => x.Visited).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }

        /// <summary>
        /// Get newest from KeyBookNew store
        /// </summary>       
        public static List<Book> FindNew(int pageindex, bool renewCaching = false)
        {
            var pr = new object[] { typeof(Book).Name, "FindNew", pageindex };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(pr);
                if (obj != null)
                    return (List<Book>)obj;
            }
            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => x.Status).OrderByDescending(x => x.CreatedDate).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                CacheHelper.Instance.Set(pr, results);
                return results;
            }
        }
        /// <summary>
        /// Lay danh sach moi cap nhat, giam dan theo ngay cap nhat
        /// </summary>
        /// <param name="pageindex"></param>
        /// <param name="renewCaching"></param>
        /// <returns></returns>
        public static List<Book> FindLatestUpdate(int pageindex, bool renewCaching = false)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Book).Name, "FindLatestUpdate", pageindex };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Book>)obj;

            //end get cache -------------------
            using (var context = new CmsDataContext())
            {
                var results = context.Books.Where(x => x.Status).OrderByDescending(x => x.ModifiedDate).Skip(PageSizeDefault * pageindex).Take(PageSizeDefault).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }
        public static void IncreateView(int bookId)
        {
            LINQHelper.ExecuteNonQuery("UPDATE [Book] set Visited=Visited+1 WHERE ID={0}", bookId);
        }
        public static List<Book> FindMostViewToDay(int pageindex, out int count, bool renewCaching = false)
        {
            List<Book> results;
            var parameter = new object[] { typeof(Book).Name, "FindMostViewToDay" };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                {
                    results = (List<Book>)obj;
                    count = results.Count;
                    return results.Skip(pageindex * PageSizeDefault).Take(PageSizeDefault).ToList();
                }
            }
            var ids = SecurityProvider.Instance.GetHotIdVisited(PageSizeDefault * 10);// chi lay 10 page
            var arrIds = ToIntArray(ids);

            using (var context = new CmsDataContext())
            {
                results = context.Books.Where(x => arrIds.Contains(x.ID) && x.Status).ToList();
            }
            count = results.Count;
            if (count > 0)
                CacheHelper.Instance.Set(parameter, results);
            return results.Skip(pageindex * PageSizeDefault).Take(PageSizeDefault).ToList();
        }

        /// <summary>
        /// Cập nhật lại chương cuối và tổng chương
        /// nếu totalChap=-1 thì không cập nhật tổng chương
        /// </summary>
        /// <param name="lastChapId"></param>
        /// <param name="totalChap"></param>
        public void ChangeLastChap(int lastChapId, int totalChap = -1)
        {
            LastChapId = lastChapId;
            ModifiedDate = DateTime.Now;
            if (totalChap >= 0)
            {
                TotalChap = totalChap;
                Status = totalChap > 0;
            }
            UpdateOnly();
            Recache(this);
        }
        public void UpdateRate()
        {
            using (var context = new CmsDataContext())
            {
                context.ExecuteCommand("UPDATE Book set RateTotal={0}, RateAverage={1} WHERE ID={2}", RateTotal, RateAverage, ID);
            }
            Recache(this);
        }
        public void UpdateOnly()
        {
            using (var context = new CmsDataContext())
            {
                context.GetTable(GetType()).Attach(this);
                context.Refresh(RefreshMode.KeepCurrentValues, this);
                context.SubmitChanges();
            }
            Recache(this);
        }
        public override void BeforeSaveUpdate()
        {
            if (OtherName != null)
            {
                if (OtherName.Length > 500)
                    OtherName = OtherName.Substring(0, 499);
                OtherSortName = GetSortName(OtherName);
            }
            base.BeforeSaveUpdate();
        }
        public override void AfterSaveUpdate()
        {
            base.AfterSaveUpdate();
            //update list book by Author if Changed Author
            var oldAuthorId = (OriginalData != null) ? ((Book)OriginalData).AuthorId : 0;
            if (AuthorId > 0 && AuthorId != oldAuthorId)
            {
                BookByAuthor.ChangedBookByAuthor(ID, AuthorId, oldAuthorId);
            }

            Recache(this);
        }
        public override void AfterDelete()
        {
            //remove ref from BookByAuthor
            BookByAuthor.RemoveBookByAuthor(AuthorId, ID);
            //remove all chaps
            LINQHelper.ExecuteNonQuery("DELETE CONTENT WHERE Book_ID={0}", ID);
            //remove all folders and files from book
            FileHelper.RemoveFolder(string.Format(Content.FolderPathFormat, ID));

            base.AfterDelete();
        }
        public static void Recache(Book obj)
        {
            FindByPrimaryKey<Book>(obj.ID, true);
            //clear book static
            var keyUniq = Process.GetUniqueUrl(obj.Url);
            CacheHelper.Instance.Set(keyUniq, null);
        }
        #endregion

        #region logic
        public static string GetAuthor(object authorId)
        {
            var author = Author.FindByPrimaryKey(authorId);
            if (author != null)
            {
                return string.Format("<a href='{0}' title='{1}' class='author'>{1}</a>", author.Url, author.FullName);
            }
            return "";
        }
        public static string GetIconHot(object ishot)
        {
            if ((bool)ishot)
            {
                return "<div class='iconhot'></div>";
            }
            return "";
        }

        //        public static string BuilTypeList(string types)
        //        {
        //            var split = types.Split(',');
        //            var result = split.Select(s => HistoryType.FindByName(s.Trim())).Where(bt => bt != null).Aggregate("", (current, bt) => current + string.Format("<a href='{0}' class='booktype'>{1}</a>, ", bt.Url, bt.FullName));
        //            return string.IsNullOrEmpty(result) ? "" : result.Remove(result.Length - 2);
        //        }

        const string ImgSizePattern = @"/(w|s|h)(\d+)/";
        public static string RenderImage(string image, int width, string title = "", bool getLinkOnly = true)
        {
            var parser = new Crawler.HtmlParser();
            string link;
            string html;
            var fiximg = image ?? "";   //do not null
            var fiximglower = fiximg.ToLower();
            if (parser.Matched(fiximglower, ("google.com|blogspot.com|googleusercontent.com")))
            {
                var replacer = string.Format("/w{0}/", width);
                var idx = fiximg.IndexOf("?");
                if (idx > 0)
                    fiximg = fiximg.Substring(0, idx);

                link = parser.Replace(fiximg, ImgSizePattern, replacer);
                if (link.IndexOf(replacer) < 0)
                {
                    idx = link.LastIndexOf("/");
                    link = link.Insert(idx, replacer.Remove(replacer.Length - 1));
                }
                html = string.Format("<img src=\"{0}\" title=\"{1}\" width={2} />", link, title, width);
            }
            else if (fiximglower.StartsWith("http"))
            {
                link = fiximg;
                html = string.Format("<img src=\"{0}\" title=\"{1}\" width={2} />", link, title, width);
            }
            else
            {
                link = ImageHelper.Instance.GetRealPath(fiximg, width);
                html = string.Format("<img src=\"{0}\" title=\"{1}\" width={2} />", link, title, width);
            }
            return getLinkOnly ? link : html;
        }
        #endregion
    }

    public class BookSearchObject
    {
        public BookSearchObject()
        {
            Status = -1;
            FindHot = -1;
            FindFinish = -1;
            FindUniquee = -1;
            OrderByFieldName = "FullName";
            OrderByAct = "Asc";
        }

        public string Key { get; set; }
        public int BookTypeId { get; set; }
        public int Status { get; set; }
        public int FindHot { get; set; }
        public int FindFinish { get; set; }
        public int FindUniquee { get; set; }
        public string CrawlHost { get; set; }
        public int Author { get; set; }

        public string OrderByFieldName { get; set; }
        public string OrderByAct { get; set; }
    }
}